package cn.edu.scau.cmi.ema.service;

import cn.edu.scau.cmi.ema.base.CmiPagination;
import cn.edu.scau.cmi.ema.domain.*;
import cn.edu.scau.cmi.ema.service.base.CmiEmaService;
import cn.edu.scau.cmi.ema.util.CmiSqlUtil;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Query;
import java.util.*;
@Service("AddressService")
@Transactional

public class AddressServiceImpl extends CmiEmaService implements AddressService {
	public AddressServiceImpl() {
	}

	@Transactional
	public Address saveAddress(Address address) {
		Set<Additiveaddress> existingAdditiveaddresssForAddress = new HashSet<Additiveaddress>();
		Set<Address> existingAddresssForParent = new HashSet<Address>();
		Set<Detectdepartment> existingDetectdepartmentsForAddress = new HashSet<Detectdepartment>();
		Set<Event> existingEventsForSourceAddress = new HashSet<Event>();
		Set<Event> existingEventsForDiscoveryAddress = new HashSet<Event>();
		Set<Foodsaleaddress> existingFoodsaleaddresssForAddress = new HashSet<Foodsaleaddress>();
		Set<Materialaddress> existingMaterialaddresssForAddress = new HashSet<Materialaddress>();
		Set<Producer> existingProducersForAddress = new HashSet<Producer>();
		Set<Supplier> existingSuppliersForAddress = new HashSet<Supplier>();

		Set<Additiveaddress> selectedAdditiveaddresssForAddress = address.getAdditiveaddresssForAddress();
		Set<Address> selectedAddresssForParent = address.getAddresssForParent();
		Set<Detectdepartment> selectedDetectdepartmentsForAddress = address.getDetectdepartmentsForAddress();
		Set<Event> selectedEventsForSourceAddress = address.getEventsForSourceAddress();
		Set<Event> selectedEventsForDiscoveryAddress = address.getEventsForDiscoveryAddress();
		Set<Foodsaleaddress> selectedFoodsaleaddresssForAddress = address.getFoodsaleaddresssForAddress();
		Set<Materialaddress> selectedMaterialaddresssForAddress = address.getMaterialaddresssForAddress();
		Set<Producer> selectedProducersForAddress = address.getProducersForAddress();
		Set<Supplier> selectedSuppliersForAddress = address.getSuppliersForAddress();

		if (address.getId() == null) {
			Address parent = addressDAO.findAddressById(address.getParent().getId());

			Address existingAddress = addressDAO.findAddressByPhysicalPrimaryKey(address.getName(), parent.getId());

			address.setParent(parent);

			if (isEquals(address, existingAddress)) {
				return existingAddress;
			}

			address = addressDAO.store(address);
			addressDAO.flush();

			for (Additiveaddress relatedAdditiveaddress : selectedAdditiveaddresssForAddress) {
				relatedAdditiveaddress.setAddress(address);
				additiveaddressDAO.store(relatedAdditiveaddress);
			}
			for (Address relatedAddress : selectedAddresssForParent) {
				relatedAddress.setParent(address);
				addressDAO.store(relatedAddress);
			}
			for (Detectdepartment relatedDetectdepartment : selectedDetectdepartmentsForAddress) {
				relatedDetectdepartment.setAddress(address);
				detectdepartmentDAO.store(relatedDetectdepartment);
			}
			for (Event relatedEvent : selectedEventsForSourceAddress) {
				relatedEvent.setSourceAddress(address);
				eventDAO.store(relatedEvent);
			}
			for (Event relatedEvent : selectedEventsForDiscoveryAddress) {
				relatedEvent.setDiscoveryAddress(address);
				eventDAO.store(relatedEvent);
			}
			for (Foodsaleaddress relatedFoodsaleaddress : selectedFoodsaleaddresssForAddress) {
				relatedFoodsaleaddress.setAddress(address);
				foodsaleaddressDAO.store(relatedFoodsaleaddress);
			}
			for (Materialaddress relatedMaterialaddress : selectedMaterialaddresssForAddress) {
				relatedMaterialaddress.setAddress(address);
				materialaddressDAO.store(relatedMaterialaddress);
			}
			for (Producer relatedProducer : selectedProducersForAddress) {
				relatedProducer.setAddress(address);
				producerDAO.store(relatedProducer);
			}
			for (Supplier relatedSupplier : selectedSuppliersForAddress) {
				relatedSupplier.setAddress(address);
				supplierDAO.store(relatedSupplier);
			}
		} else {
			Address existingAddress = addressDAO.findAddressByPrimaryKey(address.getId());

			existingAdditiveaddresssForAddress = existingAddress.getAdditiveaddresssForAddress();
			existingAddresssForParent = existingAddress.getAddresssForParent();
			existingDetectdepartmentsForAddress = existingAddress.getDetectdepartmentsForAddress();
			existingEventsForSourceAddress = existingAddress.getEventsForSourceAddress();
			existingEventsForDiscoveryAddress = existingAddress.getEventsForDiscoveryAddress();
			existingFoodsaleaddresssForAddress = existingAddress.getFoodsaleaddresssForAddress();
			existingMaterialaddresssForAddress = existingAddress.getMaterialaddresssForAddress();
			existingProducersForAddress = existingAddress.getProducersForAddress();
			existingSuppliersForAddress = existingAddress.getSuppliersForAddress();

			Set<Additiveaddress> prepareDeleteAdditiveaddresssForAddress = new HashSet<Additiveaddress>();
			Set<Additiveaddress> prepareAddAdditiveaddresssForAddress = new HashSet<Additiveaddress>();

			prepareDeleteAdditiveaddresssForAddress.addAll(selectedAdditiveaddresssForAddress);
			prepareDeleteAdditiveaddresssForAddress.addAll(existingAdditiveaddresssForAddress);

			prepareAddAdditiveaddresssForAddress.addAll(selectedAdditiveaddresssForAddress);
			prepareAddAdditiveaddresssForAddress.addAll(existingAdditiveaddresssForAddress);

			prepareDeleteAdditiveaddresssForAddress.removeAll(selectedAdditiveaddresssForAddress);
			prepareAddAdditiveaddresssForAddress.removeAll(existingAdditiveaddresssForAddress);

			for (Additiveaddress relatedAdditiveaddress : prepareAddAdditiveaddresssForAddress) {
				relatedAdditiveaddress.setAddress(address);
				additiveaddressDAO.store(relatedAdditiveaddress);
			}

			for (Additiveaddress relatedAdditiveaddress : prepareDeleteAdditiveaddresssForAddress) {
				relatedAdditiveaddress.setAddress(null);
				additiveaddressDAO.store(relatedAdditiveaddress);
			}
			Set<Address> prepareDeleteAddresssForParent = new HashSet<Address>();
			Set<Address> prepareAddAddresssForParent = new HashSet<Address>();

			prepareDeleteAddresssForParent.addAll(selectedAddresssForParent);
			prepareDeleteAddresssForParent.addAll(existingAddresssForParent);

			prepareAddAddresssForParent.addAll(selectedAddresssForParent);
			prepareAddAddresssForParent.addAll(existingAddresssForParent);

			prepareDeleteAddresssForParent.removeAll(selectedAddresssForParent);
			prepareAddAddresssForParent.removeAll(existingAddresssForParent);

			for (Address relatedAddress : prepareAddAddresssForParent) {
				relatedAddress.setParent(address);
				addressDAO.store(relatedAddress);
			}

			for (Address relatedAddress : prepareDeleteAddresssForParent) {
				relatedAddress.setParent(null);
				addressDAO.store(relatedAddress);
			}
			Set<Detectdepartment> prepareDeleteDetectdepartmentsForAddress = new HashSet<Detectdepartment>();
			Set<Detectdepartment> prepareAddDetectdepartmentsForAddress = new HashSet<Detectdepartment>();

			prepareDeleteDetectdepartmentsForAddress.addAll(selectedDetectdepartmentsForAddress);
			prepareDeleteDetectdepartmentsForAddress.addAll(existingDetectdepartmentsForAddress);

			prepareAddDetectdepartmentsForAddress.addAll(selectedDetectdepartmentsForAddress);
			prepareAddDetectdepartmentsForAddress.addAll(existingDetectdepartmentsForAddress);

			prepareDeleteDetectdepartmentsForAddress.removeAll(selectedDetectdepartmentsForAddress);
			prepareAddDetectdepartmentsForAddress.removeAll(existingDetectdepartmentsForAddress);

			for (Detectdepartment relatedDetectdepartment : prepareAddDetectdepartmentsForAddress) {
				relatedDetectdepartment.setAddress(address);
				detectdepartmentDAO.store(relatedDetectdepartment);
			}

			for (Detectdepartment relatedDetectdepartment : prepareDeleteDetectdepartmentsForAddress) {
				relatedDetectdepartment.setAddress(null);
				detectdepartmentDAO.store(relatedDetectdepartment);
			}
			Set<Event> prepareDeleteEventsForSourceAddress = new HashSet<Event>();
			Set<Event> prepareAddEventsForSourceAddress = new HashSet<Event>();

			prepareDeleteEventsForSourceAddress.addAll(selectedEventsForSourceAddress);
			prepareDeleteEventsForSourceAddress.addAll(existingEventsForSourceAddress);

			prepareAddEventsForSourceAddress.addAll(selectedEventsForSourceAddress);
			prepareAddEventsForSourceAddress.addAll(existingEventsForSourceAddress);

			prepareDeleteEventsForSourceAddress.removeAll(selectedEventsForSourceAddress);
			prepareAddEventsForSourceAddress.removeAll(existingEventsForSourceAddress);

			for (Event relatedEvent : prepareAddEventsForSourceAddress) {
				relatedEvent.setSourceAddress(address);
				eventDAO.store(relatedEvent);
			}

			for (Event relatedEvent : prepareDeleteEventsForSourceAddress) {
				relatedEvent.setSourceAddress(null);
				eventDAO.store(relatedEvent);
			}
			Set<Event> prepareDeleteEventsForDiscoveryAddress = new HashSet<Event>();
			Set<Event> prepareAddEventsForDiscoveryAddress = new HashSet<Event>();

			prepareDeleteEventsForDiscoveryAddress.addAll(selectedEventsForDiscoveryAddress);
			prepareDeleteEventsForDiscoveryAddress.addAll(existingEventsForDiscoveryAddress);

			prepareAddEventsForDiscoveryAddress.addAll(selectedEventsForDiscoveryAddress);
			prepareAddEventsForDiscoveryAddress.addAll(existingEventsForDiscoveryAddress);

			prepareDeleteEventsForDiscoveryAddress.removeAll(selectedEventsForDiscoveryAddress);
			prepareAddEventsForDiscoveryAddress.removeAll(existingEventsForDiscoveryAddress);

			for (Event relatedEvent : prepareAddEventsForDiscoveryAddress) {
				relatedEvent.setDiscoveryAddress(address);
				eventDAO.store(relatedEvent);
			}

			for (Event relatedEvent : prepareDeleteEventsForDiscoveryAddress) {
				relatedEvent.setDiscoveryAddress(null);
				eventDAO.store(relatedEvent);
			}
			Set<Foodsaleaddress> prepareDeleteFoodsaleaddresssForAddress = new HashSet<Foodsaleaddress>();
			Set<Foodsaleaddress> prepareAddFoodsaleaddresssForAddress = new HashSet<Foodsaleaddress>();

			prepareDeleteFoodsaleaddresssForAddress.addAll(selectedFoodsaleaddresssForAddress);
			prepareDeleteFoodsaleaddresssForAddress.addAll(existingFoodsaleaddresssForAddress);

			prepareAddFoodsaleaddresssForAddress.addAll(selectedFoodsaleaddresssForAddress);
			prepareAddFoodsaleaddresssForAddress.addAll(existingFoodsaleaddresssForAddress);

			prepareDeleteFoodsaleaddresssForAddress.removeAll(selectedFoodsaleaddresssForAddress);
			prepareAddFoodsaleaddresssForAddress.removeAll(existingFoodsaleaddresssForAddress);

			for (Foodsaleaddress relatedFoodsaleaddress : prepareAddFoodsaleaddresssForAddress) {
				relatedFoodsaleaddress.setAddress(address);
				foodsaleaddressDAO.store(relatedFoodsaleaddress);
			}

			for (Foodsaleaddress relatedFoodsaleaddress : prepareDeleteFoodsaleaddresssForAddress) {
				relatedFoodsaleaddress.setAddress(null);
				foodsaleaddressDAO.store(relatedFoodsaleaddress);
			}
			Set<Materialaddress> prepareDeleteMaterialaddresssForAddress = new HashSet<Materialaddress>();
			Set<Materialaddress> prepareAddMaterialaddresssForAddress = new HashSet<Materialaddress>();

			prepareDeleteMaterialaddresssForAddress.addAll(selectedMaterialaddresssForAddress);
			prepareDeleteMaterialaddresssForAddress.addAll(existingMaterialaddresssForAddress);

			prepareAddMaterialaddresssForAddress.addAll(selectedMaterialaddresssForAddress);
			prepareAddMaterialaddresssForAddress.addAll(existingMaterialaddresssForAddress);

			prepareDeleteMaterialaddresssForAddress.removeAll(selectedMaterialaddresssForAddress);
			prepareAddMaterialaddresssForAddress.removeAll(existingMaterialaddresssForAddress);

			for (Materialaddress relatedMaterialaddress : prepareAddMaterialaddresssForAddress) {
				relatedMaterialaddress.setAddress(address);
				materialaddressDAO.store(relatedMaterialaddress);
			}

			for (Materialaddress relatedMaterialaddress : prepareDeleteMaterialaddresssForAddress) {
				relatedMaterialaddress.setAddress(null);
				materialaddressDAO.store(relatedMaterialaddress);
			}
			Set<Producer> prepareDeleteProducersForAddress = new HashSet<Producer>();
			Set<Producer> prepareAddProducersForAddress = new HashSet<Producer>();

			prepareDeleteProducersForAddress.addAll(selectedProducersForAddress);
			prepareDeleteProducersForAddress.addAll(existingProducersForAddress);

			prepareAddProducersForAddress.addAll(selectedProducersForAddress);
			prepareAddProducersForAddress.addAll(existingProducersForAddress);

			prepareDeleteProducersForAddress.removeAll(selectedProducersForAddress);
			prepareAddProducersForAddress.removeAll(existingProducersForAddress);

			for (Producer relatedProducer : prepareAddProducersForAddress) {
				relatedProducer.setAddress(address);
				producerDAO.store(relatedProducer);
			}

			for (Producer relatedProducer : prepareDeleteProducersForAddress) {
				relatedProducer.setAddress(null);
				producerDAO.store(relatedProducer);
			}
			Set<Supplier> prepareDeleteSuppliersForAddress = new HashSet<Supplier>();
			Set<Supplier> prepareAddSuppliersForAddress = new HashSet<Supplier>();

			prepareDeleteSuppliersForAddress.addAll(selectedSuppliersForAddress);
			prepareDeleteSuppliersForAddress.addAll(existingSuppliersForAddress);

			prepareAddSuppliersForAddress.addAll(selectedSuppliersForAddress);
			prepareAddSuppliersForAddress.addAll(existingSuppliersForAddress);

			prepareDeleteSuppliersForAddress.removeAll(selectedSuppliersForAddress);
			prepareAddSuppliersForAddress.removeAll(existingSuppliersForAddress);

			for (Supplier relatedSupplier : prepareAddSuppliersForAddress) {
				relatedSupplier.setAddress(address);
				supplierDAO.store(relatedSupplier);
			}

			for (Supplier relatedSupplier : prepareDeleteSuppliersForAddress) {
				relatedSupplier.setAddress(null);
				supplierDAO.store(relatedSupplier);
			}
			address = addressDAO.store(address);
			addressDAO.flush();
		}

		additiveaddressDAO.flush();
		addressDAO.flush();
		detectdepartmentDAO.flush();
		eventDAO.flush();
		eventDAO.flush();
		foodsaleaddressDAO.flush();
		materialaddressDAO.flush();
		producerDAO.flush();
		supplierDAO.flush();
		return address;
	}

	public void deleteAddress(Address address) {
		addressDAO.remove(address);
		addressDAO.flush();
	}

	@Transactional
	public void deleteAddresss(Set<Integer> deleteAddresssId) {
		Query query = addressDAO.createNamedQuery("deleteAddresssById", -1, -1);
		query.setParameter(1, deleteAddresssId);
		query.executeUpdate();
	}

	@Transactional
	public Set<Address> loadAddresss() {
		return addressDAO.findAllAddresss();

	}

	@Transactional
	public Set<Address> loadReferenceAddresss(Set<Address> addresss) {
		Iterator<Address> addresssIterator = addresss.iterator();
		Set<Address> referencedAddresss = new HashSet<>();
		while (addresssIterator.hasNext()) {
			Address currentAddress = addresssIterator.next();

			if (!currentAddress.getAdditiveaddresssForAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getAddresssForParent().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getDetectdepartmentsForAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getEventsForSourceAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getEventsForDiscoveryAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getFoodsaleaddresssForAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getMaterialaddresssForAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getProducersForAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}

			if (!currentAddress.getSuppliersForAddress().isEmpty()) {
				referencedAddresss.add(currentAddress);
				continue;
			}
		}
		return referencedAddresss;
	}

	public Set<Address> loadAddresssByIdSet(Set<Integer> addressIdSet) {

		Iterator<Integer> addressIdIterator = addressIdSet.iterator();
		Set<Address> addressSet = new HashSet<>();
		while (addressIdIterator.hasNext()) {
			Integer id = addressIdIterator.next();
			Address currentAddress = addressDAO.findAddressById(id);
			addressSet.add(currentAddress);
		}
		return addressSet;
	}

	@Transactional
	public List<Address> findAllAddresss(Integer startResult, Integer maxRows) {
		return new java.util.ArrayList<Address>(addressDAO.findAllAddresss(startResult, maxRows));
	}

	@Transactional
	public Address findAddressByPrimaryKey(Integer id) {
		return addressDAO.findAddressByPrimaryKey(id);
	}

	@Transactional
	public Integer countAddresss() {
		return ((Long) addressDAO.createQuerySingleResult("select count(o) from Address o").getSingleResult())
				.intValue();
	}

	@Transactional
	public Integer countAddresss(CmiPagination page) {
		String sql = CmiSqlUtil.createCountHql(page, Address.class);
		return ((Long) addressDAO.createQuerySingleResult(sql).getSingleResult()).intValue();
	}

	/**
	 * search for name with ajax
	 *
	 */
	@Transactional
	public Integer countAddresssByName(String queryName) {
		Query query = addressDAO.createNamedQuery("countAddresssByName", -1, -1);
		query.setParameter(1, queryName);
		return new Long((long) query.getSingleResult()).intValue();
	}

	@Transactional
	public Integer countAddresssByDetachedCriteria(CmiPagination page) {
		DetachedCriteria detachedCriteria = CmiSqlUtil.createDetachedCriteria(page, Address.class);
		Session session = (Session) entityManager.getDelegate();
		detachedCriteria.setProjection(Projections.rowCount());
		return new Long((long) detachedCriteria.getExecutableCriteria(session).uniqueResult()).intValue();
	}

	public Map<String, ?> loadAddressObjectReferencedSet(Address address) {
		HashMap<String, Object> objectReferencedSetMap = new HashMap<>();
		if (address.getParent() != null) {

			Set<Integer> parentIds = new HashSet<Integer>();
			parentIds.add(address.getParent().getId());
			objectReferencedSetMap.put("parentIds", parentIds);
		}

		Set<Integer> additiveaddresssIds = new HashSet<Integer>();
		for (Additiveaddress obj : address.getAdditiveaddresssForAddress()) {

			additiveaddresssIds.add(obj.getId());
		}
		objectReferencedSetMap.put("additiveaddresssIds", additiveaddresssIds);
		Set<Integer> addresssIds = new HashSet<Integer>();
		for (Address obj : address.getAddresssForParent()) {

			addresssIds.add(obj.getId());
		}
		objectReferencedSetMap.put("addresssIds", addresssIds);
		Set<Integer> detectdepartmentsIds = new HashSet<Integer>();
		for (Detectdepartment obj : address.getDetectdepartmentsForAddress()) {

			detectdepartmentsIds.add(obj.getId());
		}
		objectReferencedSetMap.put("detectdepartmentsIds", detectdepartmentsIds);
		Set<Integer> eventsIds = new HashSet<Integer>();
		for (Event event : address.getEventsForSourceAddress()) {
			eventsIds.add(event.getId());
		}
		objectReferencedSetMap.put("eventsIds", eventsIds);
		Set<Integer> foodsaleaddresssIds = new HashSet<Integer>();
		for (Foodsaleaddress obj : address.getFoodsaleaddresssForAddress()) {

			foodsaleaddresssIds.add(obj.getId());
		}
		objectReferencedSetMap.put("foodsaleaddresssIds", foodsaleaddresssIds);
		Set<Integer> materialaddresssIds = new HashSet<Integer>();
		for (Materialaddress obj : address.getMaterialaddresssForAddress()) {

			materialaddresssIds.add(obj.getId());
		}
		objectReferencedSetMap.put("materialaddresssIds", materialaddresssIds);
		Set<Integer> producersIds = new HashSet<Integer>();
		for (Producer obj : address.getProducersForAddress()) {

			producersIds.add(obj.getId());
		}
		objectReferencedSetMap.put("producersIds", producersIds);
		Set<Integer> suppliersIds = new HashSet<Integer>();
		for (Supplier obj : address.getSuppliersForAddress()) {

			suppliersIds.add(obj.getId());
		}
		objectReferencedSetMap.put("suppliersIds", suppliersIds);

		return objectReferencedSetMap;
	}

	public Map<String, ?> loadAddressClassReferencedSet(Address address) {
		HashMap<String, Object> classReferencedSetMap = new HashMap<>();
		classReferencedSetMap.put("additiveaddresss", additiveaddressDAO.findAllAdditiveaddresss());
		classReferencedSetMap.put("addresss", addressDAO.findAllAddresss());
		classReferencedSetMap.put("detectdepartments", detectdepartmentDAO.findAllDetectdepartments());
		classReferencedSetMap.put("events", eventDAO.findAllEvents());
		classReferencedSetMap.put("foodsaleaddresss", foodsaleaddressDAO.findAllFoodsaleaddresss());
		classReferencedSetMap.put("materialaddresss", materialaddressDAO.findAllMaterialaddresss());
		classReferencedSetMap.put("producers", producerDAO.findAllProducers());
		classReferencedSetMap.put("suppliers", supplierDAO.findAllSuppliers());

		return classReferencedSetMap;
	}

	public boolean isEquals(Address address, Object obj) {
		if (obj == address)
			return true;
		if (!(obj instanceof Address))
			return false;
		Address comparedAddress = (Address) obj;
		if (!CheckPropertyEqual(address.getName(), comparedAddress.getName())) {
			return false;
		}
		if (!CheckPropertyEqual(address.getParent().getId(), comparedAddress.getParent().getId())) {
			return false;
		}
		return true;
	}

	public static <T> boolean CheckPropertyEqual(T originProperty, T comparedProperty) {
		// 基本类型经过参数传递后会自动装箱
		String[] className = originProperty.getClass().getName().split("\\.");
		if (className[0].equals("java") && className[1].equals("math")) {
			return ((Comparable) originProperty).compareTo(comparedProperty) == 0 ? true : false;
		} else {
			return originProperty.equals(comparedProperty);
		}
	}
}

